package platform.user.auth.config;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.access.vote.AuthenticatedVoter;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import platform.user.auth.UserDetailsAuthenticationProvider;
import platform.user.auth.token.TokenAuthenticationEntryPoint;
import platform.user.auth.token.TokenAuthenticationFilter;
import platform.user.auth.token.TokenGenerator;
import platform.user.auth.token.store.PersistentTokenStore;
import platform.user.auth.token.store.TokenStore;

import javax.servlet.Filter;
import java.util.*;

/**
 * 认证、鉴权配置
 *
 * @author roamer
 * @version V1.0
 * @date 2019-03-10 16:14
 */
@Configuration
@ImportResource("classpath:auth.xml")
@EnableMongoRepositories("platform.user.auth")
public class AuthConfigContext {

    @Value("${auth.uri.white_list:none}")
    private String authWhiteList;

    @Bean(name = "expressionHandler")
    public DefaultMethodSecurityExpressionHandler expressionHandler() {
        return new DefaultMethodSecurityExpressionHandler();
    }

    @Bean
    public FilterChainProxy filterChainProxy() {
        List<SecurityFilterChain> filterChains = new ArrayList<>();
        List<Filter> insecureFilters = insecureFilters();
        if (StringUtils.isNoneBlank(authWhiteList) && !"none".equals(authWhiteList)) {

            Arrays.stream(authWhiteList.split(",")).forEach(authUri -> {
                SecurityFilterChain whiteListFilterChain = new DefaultSecurityFilterChain(
                        new AntPathRequestMatcher(authUri), insecureFilters);
                filterChains.add(whiteListFilterChain);
            });
        }

        List<Filter> secureFilters = new ArrayList<>();
        secureFilters.add(tokenAuthenticationFilter());
        secureFilters.add(tokenExceptionTranslationFilter());
        secureFilters.add(filterSecurityInterceptor());


        SecurityFilterChain filterChain = new DefaultSecurityFilterChain(new AntPathRequestMatcher("/**"),
                                                                         secureFilters);
        filterChains.add(filterChain);
        return new FilterChainProxy(filterChains);
    }

    @Bean(name = "insecureFilters")
    public List<Filter> insecureFilters() {
        return new ArrayList<>();
    }

    @Bean
    public TokenAuthenticationFilter tokenAuthenticationFilter() {
        return new TokenAuthenticationFilter();
    }

    @Bean
    public ExceptionTranslationFilter tokenExceptionTranslationFilter() {
        return new ExceptionTranslationFilter(authenticationEntryPoint());
    }

    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        return new TokenAuthenticationEntryPoint();
    }

    @Bean
    public FilterSecurityInterceptor filterSecurityInterceptor() {
        FilterSecurityInterceptor interceptor = new FilterSecurityInterceptor();
        interceptor.setAuthenticationManager(authenticationManager());
        interceptor.setAccessDecisionManager(accessDecisionManager());
        interceptor.setSecurityMetadataSource(securityMetadataSource());
        interceptor.setValidateConfigAttributes(false);
        return interceptor;
    }

    @Bean
    public ProviderManager authenticationManager() {
        List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
        authenticationProviders.add(userDetailsAuthenticationProvider());
        return new ProviderManager(authenticationProviders);
    }

    @Bean
    public UserDetailsAuthenticationProvider userDetailsAuthenticationProvider() {
        return new UserDetailsAuthenticationProvider();
    }

    @Bean
    public AccessDecisionManager accessDecisionManager() {
        return new AffirmativeBased(Collections.singletonList(new AuthenticatedVoter()));
    }

    @Bean
    public FilterInvocationSecurityMetadataSource securityMetadataSource() {
        LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<>();
        requestMap.put(AnyRequestMatcher.INSTANCE, SecurityConfig.createList(AuthenticatedVoter.IS_AUTHENTICATED_FULLY,
                                                                             AuthenticatedVoter.IS_AUTHENTICATED_REMEMBERED));
        return new DefaultFilterInvocationSecurityMetadataSource(requestMap);
    }

    @Bean(name = "sessionTokenGenerator")
    public TokenGenerator tokenGenerator() {
        return new TokenGenerator();
    }

    @Bean(name = "sessionsTokenStore")
    public TokenStore sessionTokenStore() {
        return new PersistentTokenStore();
    }
}
